package com.xiaoyu.base.view.list.util.diff;

import android.os.Handler;
import android.os.Message;

import androidx.recyclerview.widget.DiffUtil;

import java.lang.ref.WeakReference;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import in.srain.cube.util.CLog;

public class BackgroundDiffExecutor<T> implements Runnable {

    private static final String TAG = BackgroundDiffExecutor.class.getSimpleName();

    private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
    private final Queue<DiffPayload> mPendingQueue = new ArrayDeque<>();
    private final Handler mMainLooperHandler = new MainLooperHandler<>(this);
    private final DiffAdapter<T> mAdapter;

    private volatile boolean mRunning;
    private List<T> mPreNewList;

    BackgroundDiffExecutor(DiffAdapter<T> adapter) {
        this.mAdapter = adapter;
    }

    void enqueue(DiffPayload payload) {
        synchronized (this) {
            mPendingQueue.add(payload);
            if (!mRunning) {
                mRunning = true;
                mExecutorService.execute(this);
            }
        }
    }

    @Override
    public void run() {
        try {
            CLog.d(TAG, "run");
            while (!mPendingQueue.isEmpty()) {
                DiffPayload payload = mPendingQueue.poll();
                calculateDiff(payload);
            }
        } finally {
            mRunning = false;
        }
    }

    private void calculateDiff(DiffPayload payload) {
        if (payload == null) {
            return;
        }
        CLog.d(TAG, "start calculate");
        List<T> newList = (List<T>) payload.getNewList();

        DiffUtil.Callback diffCallback = createDiffCallback(newList);
        DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
        payload.setDiffResult(diffResult);

        updatePreNewList(newList);

        CLog.d(TAG, "send result to main");
        mMainLooperHandler.obtainMessage(0, payload).sendToTarget();
    }

    private void handleResult(DiffPayload payload) {
        CLog.d(TAG, "handleResult");
        mAdapter.applyResult(payload);
    }

    private DiffUtil.Callback createDiffCallback(List<T> newList) {
        List<T> oldList = mPreNewList != null ? mPreNewList : mAdapter.getList();
        return mAdapter.createDiffCallBack(oldList, newList);
    }

    private void updatePreNewList(List<T> list) {
        if (mPreNewList == null) {
            mPreNewList = new ArrayList<>();
        }
        mPreNewList.clear();
        mPreNewList.addAll(list);
    }

    private static class MainLooperHandler<T> extends Handler {

        private final WeakReference<BackgroundDiffExecutor<T>> mPoster;

        private MainLooperHandler(BackgroundDiffExecutor<T> poster) {
            mPoster = new WeakReference<>(poster);
        }

        @Override
        public void handleMessage(Message msg) {
            final BackgroundDiffExecutor<T> poster = mPoster.get();
            if (poster != null) {
                DiffPayload payload = (DiffPayload) msg.obj;
                poster.handleResult(payload);
            }
        }
    }
}
